/*
 * Copyright (c) 2021 Talkweb Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ntpUdp.h"
#include "hi_wifi_api.h"
#include "lwip/ip_addr.h"
#include "lwip/netifapi.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "lwip/inet.h"
#include "lwip/opt.h"
#include "lwip/sntp.h"

#define THREAD_UDP_STACK_SIZE 4096
#define TIME_OFFSET 8*3600
#define SEVENZYYEARS 2208988800UL

#define UDP_PACKET_SIZE 48
#define IP_BUFFER_SIZE 16

volatile int64_t currentEpochTime = 0;
uint32_t ntpSocket = -1;
fd_set read_set;

uint8_t sendBuf[READ_RECV_BUFFER_SIZE] = {0};
uint8_t recvBuf[READ_RECV_BUFFER_SIZE] = {0};
uint8_t ipAddress[16] = {0};
uint8_t timeString[48] = {0};
volatile BOOL udpThreadActive = FALSE;

BOOL GetThreadRunStatus()
{
    return udpThreadActive;
}

void SetThreadRunStatus(BOOL status)
{
    udpThreadActive = status;
}


int64_t GetEpochTime()
{
    return currentEpochTime;
}

char* GetEpochTimeString(TIME_FORMAT format,const char* hostname, uint16_t port)
{
    currentEpochTime = 0;
    if (StartUdpConnect(hostname,port))
	{
        if (format == HOUR_MIN_SEC) 
		{
            memset(timeString, 0, 48);
            long rawTime = GetEpochTime() + TIME_OFFSET;//getEpochTime();
            int hours = (rawTime % 86400L) / 3600;
            int minutes = (rawTime % 3600) / 60;
            int seconds = rawTime % 60;
            if (sprintf_s(timeString,48,"%02d:%02d:%02d",hours, minutes, seconds) == -1) 
			{
                printf("sprintf time error\n");
			    return NULL;
            }
        } 
		else if (format == YEAR_MONTH_DAY) 
		{
			memset(timeString, 0, 48);
			long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
			struct tm* time_tm = NULL;
			time_tm = gmtime(&rawTime);
			if (sprintf_s(timeString,48,"%04d/%02d/%02d",time_tm->tm_year+1900, time_tm->tm_mon+1, time_tm->tm_mday) == -1) 
			{
				printf("sprintf time error\n");
				return NULL;
			}
	    } 
		else if (format == YEAR_MONTH_DAY_HOUR_MIN_SEC) 
		{
			memset(timeString, 0, 48);
			long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
			struct tm* time_tm = NULL;
			time_tm = gmtime(&rawTime);
			if (sprintf_s(timeString,48,"%04d/%02d/%02d %02d:%02d:%02d",time_tm->tm_year+1900, time_tm->tm_mon+1,time_tm->tm_mday, 
																		time_tm->tm_hour,time_tm->tm_min,time_tm->tm_sec) == -1)
			{
                printf("sprintf time error\n");
			    return NULL;
			}
	    } 
		else 
		{
            // do nothing
            return NULL;
	    }
	    return timeString;
	}

    return NULL;
}

BOOL GetTimeTm(const char* hostname, const uint16_t port, struct tm * time_tm)

{
    if (hostname == NULL || time_tm == NULL) 
	{
        printf("time tm is NULL\n");
		return FALSE;
	}

    if (StartUdpConnect(hostname,port))
    {
        memset(timeString, 0, 48);
	    long rawTime = GetEpochTime() + TIME_OFFSET; //getEpochTime();
	    struct tm* time_temp = NULL;
	    time_temp = gmtime(&rawTime);
		time_tm->tm_sec = time_temp->tm_sec;
        time_tm->tm_min = time_temp->tm_min;
        time_tm->tm_hour = time_temp->tm_hour;
        time_tm->tm_mday = time_temp->tm_mday;
        time_tm->tm_mon = time_temp->tm_mon;
        time_tm->tm_year = time_temp->tm_year;
        time_tm->tm_wday = time_temp->tm_wday;
        time_tm->tm_yday = time_temp->tm_yday;
        time_tm->tm_isdst = time_temp->tm_isdst;
		printf("GetTimeTm:%04d/%02d/%02d %02d:%02d:%02d\n",time_tm->tm_year+1900, time_tm->tm_mon+1,time_tm->tm_mday,time_tm->tm_hour,time_tm->tm_min,time_tm->tm_sec);
		return TRUE;
	} 

    return FALSE;
}

void getFormattedTime(char* timeFormat, unsigned long currentEpoc)
{
    if (timeFormat == NULL) 
	{
        printf("container buffer is null\n");
		return;
	}

	memset(timeFormat, 0, 48);
	long rawTime = currentEpoc + TIME_OFFSET;//getEpochTime();
	int hours = (rawTime % 86400L) / 3600;
	int minutes = (rawTime % 3600) / 60;
	int seconds = rawTime % 60;

    if (sprintf_s(timeFormat,48,"%02d:%02d:%02d",hours,minutes,seconds) == -1) 
	{
        printf("sprintf time error\n");
    }

}

static void setSendBuf(void)
{
    memset(sendBuf,0,READ_RECV_BUFFER_SIZE);
	//sendBuf[0] = 0b11100011;   // LI, Version, Mode
	sendBuf[0] = 0xE3;
	sendBuf[1] = 0; 	// Stratum, or type of clock
	sendBuf[2] = 6; 	// Polling Interval
	sendBuf[3] = 0xEC;	// Peer Clock Precision
	// 8 bytes of zero for Root Delay & Root Dispersion
	sendBuf[12]  = 49;
	sendBuf[13]  = 0x4E;
	sendBuf[14]  = 49;
	sendBuf[15]  = 52;

}

uint32_t GetIpByHostName(const char* hostname, uint8_t* ipAddr)
{
    (void)ipAddr;
	if (!hostname || !ipAddr) return -1;
	 
	struct hostent* host=gethostbyname(hostname);
	if (!host)
	{
		return -1;
	}
	printf("HostName :%s\n",host->h_name);
	// 获取所有的地址

	if(strncpy_s(ipAddr, IP_BUFFER_SIZE, 
					inet_ntoa((*(struct in_addr*)(host->h_addr_list[0]))),
					IP_BUFFER_SIZE) != 0) 
	{
		printf("copy str error\n");
		return -1;
	} 
	else 
	{
		printf("get ip address is %s\n",ipAddr);
	}

	return 0;
}


BOOL StartUdpConnect(const char* hostname, uint16_t port)
{
    int ret;
	int nZero = 0;
    struct sockaddr_in clientaddr;
    ntpSocket = socket(PF_INET, SOCK_DGRAM, 0);
	
	if (ntpSocket == -1) 
	{
        printf("create socket fd failed\n");
	} 
	else 
	{
        printf("create socket fd success\n");
	}

	uint32_t flag = fcntl(ntpSocket, F_GETFL, 0);
	ret = fcntl(ntpSocket, F_SETFL, flag | O_NONBLOCK);
	if (ret == -1) 
	{
        printf("set nonblock failed\n");
		if (ntpSocket >= 0) close(ntpSocket);
		return FALSE;
	}


	setsockopt(ntpSocket, SOL_SOCKET,SO_RCVBUF,(char*)&nZero, sizeof(int));
	if (GetIpByHostName(hostname,ipAddress) != 0) 
	{
        printf("dns failed\n");
		if (ntpSocket >= 0) close(ntpSocket);
		return FALSE;
	} 

    int reuse = 1;
	if (setsockopt(ntpSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) 
	{
		 if (ntpSocket >= 0) close(ntpSocket);
         return FALSE;
    }

    //服务器 ip port
    bzero(&clientaddr, sizeof(clientaddr));
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = inet_addr(ipAddress);
    clientaddr.sin_port = htons(port);
    setSendBuf();
	if (sendto(ntpSocket, sendBuf,UDP_PACKET_SIZE,0,(struct sockaddr *)&clientaddr,sizeof(clientaddr)) == UDP_PACKET_SIZE) 
	{
        
	} 
	else 
	{
        printf("send ntp socket failed\n");
		if (ntpSocket >= 0) close(ntpSocket);
		return FALSE;
	}
	
    uint8_t counts = 0;
    while(udpThreadActive && counts < 100)
    {
        struct sockaddr_in serverAddr = {0};
        int sizeClientAddr = sizeof(struct sockaddr_in);
        memset(recvBuf, 0, sizeof(recvBuf));
		FD_ZERO(&read_set);
		FD_CLR(ntpSocket, &read_set);
		FD_SET(ntpSocket, &read_set);
		struct timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 10000; // 10ms
		ret = select(ntpSocket + 1, &read_set, NULL, NULL, &tv);
		if (ret > 0) 
		{
			if (FD_ISSET(ntpSocket, &read_set)) 
			{
				memset(recvBuf, 0, 50);
                ret = recvfrom(ntpSocket, recvBuf, READ_RECV_BUFFER_SIZE, 0 , (struct sockaddr*)&serverAddr,(socklen_t*)&sizeClientAddr);
                if(ret > 0)
                {
			        //short highword = (recvBuf[40]<<8) + recvBuf[41];
			        //short lowword = (recvBuf[42]<<8) + recvBuf[43];
			        //currentEpochTime = (highword << 16) + lowword;
					//currentEpochTime -=  SEVENZYYEARS;
					currentEpochTime = (unsigned long)recvBuf[40] << 24;
					currentEpochTime |= (unsigned long)recvBuf[41] << 16;
					currentEpochTime |= (unsigned long)recvBuf[42] << 8;
					currentEpochTime |= (unsigned long)recvBuf[43];
					currentEpochTime -= SEVENZYYEARS;
				    if (ntpSocket >= 0) close(ntpSocket);
			        return TRUE;
		        }
			}
          
		}
		else 
		{
            counts++;
		}
    }

	if (ntpSocket >= 0) close(ntpSocket);
	return FALSE;
}
